package com.yrd.fssync.plugin;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.eclipse.jetty.websocket.common.io.IOState;
import org.eclipse.jetty.websocket.common.util.TextUtil;

import com.yrd.fssync.core.ISyncer;
import com.yrd.fssync.core.SyncRequest;
import com.yrd.fssync.core.Utils;

@SuppressWarnings("serial")
class FtpNeedAgainException extends IOException{
	public FtpNeedAgainException(Exception e) {
		super(e);
	}
}

public class FtpSyncer implements ISyncer {
	String remoteBase;
	String hostName;
	short port;
	String userName;
	String passWord;
	FTPClient ftp;
	String charset;
	boolean alive;
	
	public FtpSyncer(String hostName, short port, String userName, String passWord,String remoteBase) {
		this.hostName = hostName;
		this.port = port;
		this.userName = userName;
		this.passWord = passWord;
		this.remoteBase = remoteBase;
		this.ftp = new FTPClient();
		this.charset = "utf-8";
	}

	public void open() throws SocketException, IOException{
		alive = true;
		ftp.connect(hostName, port);
		if (!FTPReply.isPositiveCompletion(ftp.sendCommand("OPTS UTF8", "ON"))){
			charset = "gbk";
		}
		ftp.setControlEncoding(charset);
		ftp.setFileType(FTP.BINARY_FILE_TYPE);
		FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT);
		conf.setServerLanguageCode("zh");
		ftp.login(userName, passWord);
		if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
			ftp.disconnect();
			throw new SocketException("连接服务器失败 "+this);
		}
		ftp.setFileType(FTP.BINARY_FILE_TYPE);
		ftp.enterLocalPassiveMode();
		Utils.printfln("登陆服务器成功",this);
	}
	
	public void dispose(SyncRequest request) throws Exception{
		while(alive){
			try {
				_dispose(request);
				break;
			} catch (FtpNeedAgainException  e) {
				e.printStackTrace();
				if(alive){
					Thread.sleep(1000);
					open();
				}
			}
			
		}
	}

	private void _dispose(SyncRequest request) throws IOException, Exception {
		String remotePath = remoteBase+"/"+StringUtils.defaultString(request.getRemote());
		if(request.isDir()){
			if(request.isOverwrite()){
				uploadDirs(remotePath);
			}else if(request.isDelete()){
				deleteDirs(remotePath);
			}
		}else if(request.isFile()){
			if(request.isOverwrite()){
				uploadFile(request.getLocalFile(), remotePath);
			}else if(request.isDelete()){
				deleteFile(remotePath);
			}
		}
	}
	
	
	public void uploadFile(File localFile, String remotePath) throws Exception{
		InputStream localFileInputStream = null;
		try {
			localFileInputStream = new FileInputStream(localFile);
			String localFileName = localFile.getName();
			String remoteDir = FilenameUtils.getFullPathNoEndSeparator(remotePath);
			String remoteDirFtp = toFtpString(remoteDir);
			try {
				mkdirs(remoteDirFtp);
				ftp.changeWorkingDirectory(remoteDirFtp);
				ftp.storeFile(toFtpString(localFileName), localFileInputStream);
			} catch (Exception e) {
				throw new FtpNeedAgainException(e);
			}
		}catch (Exception e) {
			throw e;
		}finally{
			IOUtils.closeQuietly(localFileInputStream);
		}
	}
	
	public void deleteFile(String remotePath ) throws Exception {
		String localFileName = FilenameUtils.getName(remotePath);
		String remoteDir = FilenameUtils.getFullPathNoEndSeparator(remotePath);
		try {
			ftp.changeWorkingDirectory(toFtpString(remoteDir));
			ftp.deleteFile(toFtpString(localFileName));
		} catch (Exception e) {
			throw new FtpNeedAgainException(e);
		}
	}
	
	
	public void uploadDirs(String remoteDir ) throws Exception {
		try {
			mkdirs(toFtpString(remoteDir));
		} catch (Exception e) {
			throw new FtpNeedAgainException(e);
		}
	}
	
	
	public void deleteDirs(String remoteDir ) throws Exception {
		try {
			rmdirs(toFtpString(remoteDir));
		} catch (Exception e) {
			throw new FtpNeedAgainException(e);
		}
	}
	
	
	public void close(){
		alive=false;
		if (ftp.isConnected()) {
			try {
				ftp.logout();
			} catch (Exception e) {
				e.printStackTrace();
			}
			try {
				ftp.disconnect();
			} catch (IOException e) {
				e.printStackTrace();
			}
			Utils.printfln("断开服务器成功",this);
		}
	}

	
	@Override
	public String toString() {
		return "FtpSyncer [remoteBase=" + remoteBase + ", hostName=" + hostName
				+ ", port=" + port + ", userName=" + userName + ", passWord="
				+ passWord + ", ftp=" + ftp + ", charset=" + charset
				+ ", alive=" + alive + "]";
	}

	private void mkdirs(String dirStr) throws IOException{
		String pwd = ftp.printWorkingDirectory();
		if(StringUtils.isEmpty(dirStr))return;
		if(dirStr.startsWith("/")){
			ftp.changeWorkingDirectory("/");
		}
		String[] dirArr = dirStr.split("/"); 
		for(String dir : dirArr){
			if(!"".equals(dir)){
				boolean res = ftp.changeWorkingDirectory(dir);
				if(!res){
					ftp.makeDirectory(dir);
					ftp.changeWorkingDirectory(dir);
				}
			}
		}
		ftp.changeWorkingDirectory(pwd);
	}
	
	private void rmdirs(String dirStr) throws IOException{
		if(StringUtils.isEmpty(dirStr))return;
		if(ftp.changeWorkingDirectory(dirStr)){
			String pwd = ftp.printWorkingDirectory();
			FTPFile[] files = ftp.listFiles();
			for(FTPFile file : files){
				if(file.isDirectory()){
					String dirName = toFtpString(file.getName());
					rmdirs(dirName);
					ftp.changeWorkingDirectory(pwd);
					ftp.removeDirectory(dirName);
				}else{
					String fileName = toFtpString(file.getName());
					ftp.deleteFile(fileName);
				}
			}
		}
	}
	
	private String toFtpString(String str) throws UnsupportedEncodingException{
		if(StringUtils.isEmpty(str))return str;
		String _str = new String(str.getBytes(charset),	"ISO-8859-1");
		return _str;
	}
}
